iOS上的端序

什么是端序

端序,也叫字节序,是指计算机上多字节数据类型的存储规则。在日常的编码中可能不太需要关心端序,但当涉及到一些比较底层的任务,例如 socket 编程时,就绕不开它。

端序(Endian)有两种规则,一种是大端序,一种是小端序。大端序是指将高位字节存放在低位地址,而小端序则是将低位字节存放在低位地址。
0x01234567为例。

大端序:

低地址 高地址
0x01 0x23 0x45 0x67

小端序:

低地址 高地址
0x67 0x45 0x23 0x01

iOS上的端序

目前为止,iOS 上采用的端序是小端序。在 NSByteOrder.h 和 OSByteOrder.h 中有许多和端序相关的定义,其中当前系统的端序可以通过 NSHostByteOrder() 获取到:

1
2
3
if (NSHostByteOrder() == NS_BigEndian) {
NSLog(@"BigEndian");
}

端序的转换

不同的设备或协议可能采用不同的端序,例如,iPhone 和 x86 架构的 PC 都采用小端序,USB协议也采用小端序;而许多网络协议,如 TCP/IP 协议,以及部分 PC 则采用大端序。此外,ARM 等平台的端序则是可以配置的。因此,当涉及到 socket、信号处理等比较底层的操作时,我们需要注意不同场合下所需的端序,按需进行转换。

不同的语言中通常都有对应的 ntoh*hton* 方法,来进行端序的转换。TCP/IP 协议中规定了数据采用大端序,因此大端序也常常被称作网络端序。相反地,小端则被称作主机端序。这也是我们常常见到的端序转换方法名 ntoh*(network-to-host)、hton*(host-to-network) 的由来。

1
2
3
4
5
// 转换端口号的端序
struct sockaddr_in addr;
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(kLocalhost);

根据数据的字节数的不同,Darwin 为我们提供了一系列常用的宏来转换端序:ntohs(16bits)、ntohl(32bits)、ntohll(64bits)、htons(16bits)、htonl(32bits)、htonll(64bits).如果需要进行更长字节数的端序转换,则需要我们自己实现。

1
2
3
4
5
6
// Int16 的端序转换宏
#define ntohs(x) __DARWIN_OSSwapInt16(x)

// 判断是否常量,使用不同的转换方式
#define __DARWIN_OSSwapInt16(x) \
((__uint16_t)(__builtin_constant_p(x) ? __DARWIN_OSSwapConstInt16(x) : _OSSwapInt16(x)))
1
2
3
4
// 常量的端序转换方法,在预处理阶段完成(uint16)
#define __DARWIN_OSSwapConstInt16(x) \
((__uint16_t)((((__uint16_t)(x) & 0xff00) >> 8) | \
(((__uint16_t)(x) & 0x00ff) << 8)))
1
2
3
4
5
6
7
8
9
// 运行时的端序转换方法(uint16)
__DARWIN_OS_INLINE
__uint16_t
_OSSwapInt16(
__uint16_t _data
)
{
return ((__uint16_t)((_data << 8) | (_data >> 8)));
}

查看这些宏的定义可以发现,Darwin 会先判断待转换的数据是否常量,如果是常量,则直接进行移位运算,在编译前的预处理阶段就完成转换;如果是变量,则替换为相应的内联方法,在运行时进行转换。